#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <pthread.h>
#define _FILE_OFFSET_BITS 64
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <cassert>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <exception>
#include <stdexcept>

/** @mainpage COPYRIGHT (C) 2014 - 2020, piggy_xrh
 *
 *	 A tool to merge the big file (version-1.0.0)
 *
 * 	  (Email: piggy_xrh@163.com  QQ: 1169732280)
 */

using namespace std;

enum 
{
	L_TRACE = 0,
	L_DEBUG,
	L_INFO,
	L_WARNING,
	L_ERROR,
};

int g_logLevel = 2;

// Create a hole to discard the debug messages

ofstream g_holeOutput;

ostream &glog(ostream &os, int level, const char *file, int line, const char *function) 
{
	static const char *s_lname[] = 
	{
		"TRACE", "DEBUG", "INFO ", "WARNING", "ERROR"
	};
	
	if (level < g_logLevel)
	{
		return g_holeOutput;
	}

	time_t cur = time(NULL);
	tm store, *res;
	char ts[50] = {0};

	res = localtime_r(&cur, &store);
	if (res)
	{
		sprintf(ts, "%04d-%02d-%02d %02d-%02d-%02d",
			res->tm_year + 1900, res->tm_mon + 1, res->tm_mday,
			res->tm_hour, res->tm_min, res->tm_sec);
	}

	return os << ts << " [" << s_lname[level] << "] |" << std::hex << pthread_self() << std::dec << "|" 
			  << file << ":" << line << ":@" << function << "::";
}

#define trace()  glog(cout, L_TRACE,   __FILE__, __LINE__, __FUNCTION__)
#define debug()  glog(cout, L_DEBUG,   __FILE__, __LINE__, __FUNCTION__)
#define info()   glog(cout, L_INFO,    __FILE__, __LINE__, __FUNCTION__)
#define warn()   glog(cerr, L_WARNING, __FILE__, __LINE__, __FUNCTION__)
#define err()    glog(cerr, L_ERROR,   __FILE__, __LINE__, __FUNCTION__)

struct CStream
{
	CStream(const char *szStreamName):
		m_szStreamName(szStreamName)
	{
	}

	virtual ~CStream()
	{
	}
	
	virtual bool    eof() = 0;
	virtual off64_t capacity() = 0;
	virtual off64_t tellp() = 0;
	virtual off64_t tellg() = 0;
	virtual char   *read(char *buf, off64_t &buflen) = 0;
	virtual off64_t write(char *buf, off64_t buflen) = 0;
	virtual string  errmsg() {return m_err.str();}

	const string& name() const {return m_szStreamName;}
	virtual string& detail(string &s) const {return s;}
protected:	
	ostream &error() {m_err.str(""); return m_err;}
private:
	string m_szStreamName;
	ostringstream m_err;
};

class CReadStream: public CStream
{
	public:
		CReadStream(const char *szStreamName):
			CStream(szStreamName) 
		{
		}
		~CReadStream()
		{
		}
		
		virtual off64_t tellp() {return 0;}
		virtual off64_t write(char *buf, off64_t buflen)
		{
			string s;
			error() << "Stream(" << name() << "/" << detail(s) << ") is read-only";
			return -1;
		}
};

class CWriteStream: public CStream
{
	public:
		CWriteStream(const char *szStreamName):
			CStream(szStreamName)
		{
		}
		~CWriteStream()
		{
		}

		virtual off64_t capacity() {return -1;}
		virtual off64_t tellg()    {return 0;}
		virtual char *read(char *buf, off64_t &buflen)
		{
			buflen = 0;
			string s;
			error() << "Stream(" << name() << "/" << detail(s) << ") is write-only";
			return NULL;
		}

		virtual bool eof() {return false;}
};

class CBufReadStream: public CReadStream
{
	public:
		CBufReadStream(char *buf, off64_t buflen, bool bRelease):
			CReadStream("buffer"), m_buf(buf), m_buflen(buflen), m_cur(0), m_bRelease(bRelease) 
		{
		}
		
		~CBufReadStream()
		{
			if (m_bRelease && m_buf)
			{
				delete [] m_buf;
			}
		}
		
		virtual string& detail(string& s) const
		{
			ostringstream os;
			
			os << std::hex << (void *)m_buf << "/" << std::dec << m_buflen << "/" << m_bRelease;
			s = os.str();

			return s;
		}

		virtual off64_t capacity()
		{
			return m_buflen;
		}

		virtual off64_t tellg()
		{
			return m_cur;
		}
		
		virtual bool eof()
		{
			return m_cur == m_buflen;
		}
	
		virtual char *read(char *buf, off64_t &buflen) 
		{
			assert (m_buflen > 0 && m_buflen >= m_cur);
			if (eof())
			{
				return NULL;
			}

			buflen = min(buflen, m_buflen - m_cur);
			m_cur += buflen;

			return m_buf;
		}
	private:
		char *m_buf;
		off64_t m_buflen;
		off64_t m_cur;
		bool m_bRelease;
};

class CFileReadStream: public CReadStream
{
	public:
		CFileReadStream(const char *szFile, off64_t off, off64_t length):
			CReadStream("fsRead"), m_szFile(szFile), m_fd(0), m_off(off), m_length(length), m_cur(0),
			m_bEof(false)
		{
			int fd = open(szFile, O_LARGEFILE|O_RDONLY);
			if (fd < 0)
			{
				error() << "open('" << szFile << "’) err:" << strerror(errno);
				throw std::runtime_error(errmsg());
			}

			struct stat64 stbuf;
			if (fstat64(fd, &stbuf) < 0)
			{
				error() << "open('" << szFile << "’) err:" << strerror(errno);
				close(fd);
				throw std::runtime_error(errmsg());
			}
			
			if (length > 0 && off + length > stbuf.st_size)
			{
				error() << "Over range(" << stbuf.st_size << ") " << szFile << " off=" << off << " length=" << length;
				close(fd);
				throw std::runtime_error(errmsg());
			}

			if (off > 0 && lseek64(fd, off, SEEK_CUR) < 0)
			{
				error() << "seek('" << szFile << "'," << off << ") err:" << strerror(errno);
				close(fd);
				throw std::runtime_error(errmsg());
			}

			if (length < 0)
			{
				m_length = stbuf.st_size - off;
			}
			
			m_fd = fd;
			m_total = stbuf.st_size;

			string s;
			debug() << "Open the stream(" << name() << "/" << detail(s) << ") successfully!" << endl; 
		}
		~CFileReadStream()
		{
			if (m_fd > 0)
			{
				close(m_fd);
			}
		}
		
		virtual string& detail(string& s) const
		{
			ostringstream os;
			
			os << "'" << m_szFile << "'/" << m_total << "/" << m_off << ":" << m_length << "/fd:" << m_fd;
			s = os.str();

			return s;
		}

		virtual bool eof() 
		{
			return m_bEof;
		}
		
		virtual off64_t capacity()
		{
			return m_length;
		}

		virtual off64_t tellg()
		{
			return m_cur;
		}

		virtual char *read(char *buf, off64_t &buflen) 
		{
			if (buflen < 0)
			{
				buflen = -1;
				error() << "invalid buflen:" << buflen;
				return NULL;
			}

			off64_t peek = buflen;

			if (m_length > 0)
			{
				assert (m_length >= m_cur);
				peek = min(buflen, m_length - m_cur);
				
				if (!peek)
				{
					buflen = 0;
					m_bEof = true;
					return NULL;
				}
			}
			
			int bytes;

			for (int i=0; i<3; i++)
			{
				bytes = ::read(m_fd, buf, peek);
				if (bytes >= 0)
				{
					if (bytes == 0)
					{
						m_bEof = true;
					}
					break;
				}
					
				error() << "read('" << m_szFile << "'," << bytes << ")  err:" << strerror(errno);
				if (EINTR == errno)
				{
					continue;
				}
				return NULL;
			}
			buflen = bytes;
			m_cur += bytes;
			
			return buflen > 0 ? buf : NULL;
		}
		
	private:
		string m_szFile;
		int  m_fd;
		off64_t m_off;
		off64_t m_length;
		off64_t m_total;
		off64_t m_cur;
		bool m_bEof;
};

class CFileWriteStream: public CWriteStream
{
	public:
		CFileWriteStream(const char *szFile, off64_t off, bool bAppend):
			CWriteStream("fsWrite"), m_szFile(szFile), m_fd(0), m_off(off), m_cur(0), m_total(0)
		{
			mode_t mode = O_LARGEFILE|O_CREAT|O_RDWR;

			if (!bAppend)
			{
				mode |= O_TRUNC;
			}
			int fd = open(szFile, mode);
			if (fd < 0)
			{
				error() << "open('" << szFile << "’) err:" << strerror(errno) << endl;
				throw std::runtime_error(errmsg());
			}

			struct stat64 stbuf;
			if (fstat64(fd, &stbuf) < 0)
			{
				error() << "open('" << szFile << "’) err:" << strerror(errno) << endl;
				close(fd);
				throw std::runtime_error(errmsg());
			}
			m_total = stbuf.st_size;
			if (off > 0)
			{
				if (lseek64(fd, off, SEEK_CUR) < 0)
				{
					error() << "lseek64('" << szFile << "'," << off << ") err:" << strerror(errno);
					close(fd);
					throw std::runtime_error(errmsg());
				}
			}
			else if (bAppend)
			{
				if (lseek64(fd, 0, SEEK_END) < 0)
				{
					error() << "lseek64('" << szFile << "'," << off << ", SEEK_END) err:" << strerror(errno);
					close(fd);
					throw std::runtime_error(errmsg());
				}
			}

			m_fd  = fd;
			m_off = off;
			
			string s;
			debug() << "Open the stream(" << name() << "/" << detail(s) << ") successfully!" << endl;
		}
		
		~CFileWriteStream()
		{
			if (m_fd > 0)
			{
				close(m_fd);
			}
		}
		
		virtual string& detail(string& s) const
		{
			ostringstream os;
			
			os << "'" << m_szFile << "'/" << m_total << "/" << m_off << "/fd:" << m_fd;
			s = os.str();

			return s;
		}

		virtual off64_t tellp()
		{
			return m_cur;
		}
		
		virtual off64_t write(char *buf, off64_t buflen) 
		{
			if (buflen < 0)
			{
				error() << "invalid buflen:" << buflen;
				return -1;
			}

			off64_t bytes = 0;
			for (int i=0; i<5 && bytes < buflen; i++)
			{
				ssize_t res = ::write(m_fd, buf + bytes, buflen - bytes);
				if (res < 0)
				{
					error() << "write(" << m_fd << "," << std::hex << (void *)(buf + bytes) << ","
							<< std::dec << buflen - bytes << ") err:" << strerror(errno);
					if (errno == EINTR)
					{
						continue;
					}
					return -1;
				}
				
				bytes += res;
				m_cur += res;
			}

			return bytes;
		}
	private:
		string m_szFile;
		int  m_fd;
		off64_t m_off;
		off64_t m_cur;
		off64_t m_total;
};

class CConsoleStream: public CStream
{
	public:
		CConsoleStream(off64_t off, off64_t length, bool bOutput = false):
			CStream(bOutput ? "stdout" : "stdin"), m_off(off), m_off_cur(0), m_length(length), m_cur(0), m_bOutput(bOutput) 
		{
			string s;
			debug() << "Open the stream(" << name() << "/" << detail(s) << ") successfully! (off:" << off 
				    << " bytes:" << length << ")" << endl;
		}
		
		~CConsoleStream()
		{
		}
		
		virtual string& detail(string& s) const
		{
			ostringstream os;
			
			os << m_off << "/" << m_length;
			s = os.str();

			return s;
		}

		virtual bool eof()
		{
			if (m_bOutput)
			{
				return false;
			}

			if (m_length > 0 && m_cur == m_length)
			{
				return true;
			}

			return cin.eof();
		}
		
		virtual off64_t capacity()
		{
			if (m_bOutput)
			{
				return -1;
			}

			return m_length;
		}

		virtual off64_t tellg()
		{
			if (m_bOutput)
			{
				return 0;
			}

			return m_cur;
		}

		virtual off64_t tellp()
		{
			if (!m_bOutput)
			{
				return 0;
			}

			return m_cur;
		}
		
		virtual char *read(char *buf, off64_t &buflen) 
		{
			int n;
			bool bEof = false;
			assert (buflen >= 2);
			
			if (m_bOutput)
			{
				string s;

				error() << "Stream(" << name() << "/" << detail(s) << ") is write-only";
				buflen = -1;
				return NULL;
			}
			
			if (!seek(bEof))
			{
				buflen = bEof ? 0 : -1;
				return NULL;
			}
						
			if (m_length > 0)
			{
				assert (m_length >= m_cur);
				n = min(buflen - 1, m_length - m_cur);
			}
			else
			{
				n = min(buflen - 1, (off64_t)1024 * 1024);
			}
			buf[n] = '\0';
			
			if ((cin.get(buf, n)))
			{
				buflen = strlen(buf);
				m_off_cur += buflen;
				m_cur += buflen;

				return buf;
			}
			
			if (cin.fail())
			{
				cin.clear();
				cin.ignore(1);
				
				buf[0] = '\n';
				buflen = 1;
				++ m_cur;
				return buf;
			}
				
			if (!cin.eof())
			{
				string s;
				error() << "stream(" << name() << "/" << detail(s) << ") is broken";
				buflen  = -1;
			}
			else
			{
				assert(cin.eof());
				buflen = 0;
			}
			return NULL;
		}
		
		virtual off64_t write(char *buf, off64_t buflen) 
		{
			string s;

			if (!m_bOutput)
			{
				error() << "Stream(" << name() << "/" << detail(s) << ") is read-only";
				return -1;
			}

			for (off64_t idx=0; idx<buflen; idx++)
			{
				m_cur ++;
				if (!(cout << buf[idx]))
				{
					error() << "stream(" << name() << "/" << detail(s) << ") is broken";
					return -1;
				}
			}

			return buflen;
		}
		
		bool seek(bool &bEof)
		{
			char buf[1024];
			
			bEof = eof();
			if (bEof)
			{
				return false;
			}

			for (;m_off_cur < m_off;)
			{
				off64_t n = min(m_off - m_off_cur, (off64_t)sizeof(buf));
				if (cin.get(buf, n))
				{
					m_off_cur += strlen(buf);
					continue;
				}

				if (cin.fail())
				{
					m_off_cur += 1;
					cin.clear();
					cin.ignore(1);
					continue;
				}

				bEof = cin.eof();
				if (!bEof)
				{
					string s;
					error() << "stream(" << name() << "/" << detail(s) << ") is broken";
				}
				return false;
			}
			
			return true;
		}
	private:
		off64_t m_off;
		off64_t m_off_cur;
		off64_t m_length;
		off64_t m_cur;
		bool m_bOutput;
};


class CTransfer
{
	public:
		static CStream *getFStream(const char *szFile, off64_t off, off64_t length, bool bReadSource, bool bAppend = false);
		static CStream *getWFStream(const char *szFile, off64_t off, bool bAppend) {return getFStream(szFile, off, -1, false, bAppend);}
		static CStream *getRFStream(const char *szFile, off64_t off, off64_t length) {return getFStream(szFile, off, length, true);}
		static CStream *getBufStream(char *buffer, off64_t length, bool bRelease);
		static CStream *getConsoleStream(off64_t off = 0, off64_t length = -1, bool bOutput = false);
		static void release(CStream *stream);	
		static bool transfer(char *buffer, int length, CStream *dfdp);
	
		CTransfer(const char *szOutputFile = "", off64_t off = 0, bool bAppend = false):
			m_dfdp(NULL), m_buf(NULL), m_buflen(0)  
		{
			if (szOutputFile)
			{
				setOutputFile(szOutputFile, off, bAppend);
			}
		}

		CTransfer(CStream *dfdp):
			m_dfdp(dfdp), m_buf(NULL), m_buflen(0)
		{
		}

		~CTransfer() 
		{
			for (FDPQ::iterator it = m_sfdpQ.begin(); it != m_sfdpQ.end(); it ++)
			{
				release(*it);
			}
			m_sfdpQ.clear();

			if (m_dfdp)
			{
				release(m_dfdp);
			}
		}
		
		inline operator bool() const {return m_dfdp != NULL && !m_sfdpQ.empty();}
		
		bool setTransferBuffer(size_t length);	
		bool setOutputFile(const char *szOutputFile, off64_t off, bool bAppend);
		bool setOutputStream(CStream *dfdp);

		bool addSource(CStream *sfdp);
		bool addFileSource(const char *szFile, off64_t off, off64_t length);
		bool addBufferSource(char *buffer, off64_t length, bool bRelease);
		bool addConsoleSource(off64_t off = 0, off64_t length = -1);
		
		bool transfer(char *buffer, off64_t length);
		bool transfer(const char *szFile, off64_t off, off64_t length);
		bool transfer();
	private:
		CStream *get();
		bool transfer(CStream *stream, off64_t &transfered);
	private:
		typedef vector<CStream *> FDPQ;
		
		FDPQ  m_sfdpQ;	
		CStream *m_dfdp;
		char *m_buf;
		off64_t m_buflen;
};

CStream *CTransfer::getFStream(const char *szFile, off64_t off, off64_t length, bool bReadSource, bool bAppend)
{
	if (off < 0)
	{
		err() << "Invalid args: " << szFile << " off=" << off << " length=" << length << endl;
		return NULL;
	}

	CStream *fdp;
	
	try
	{
		if (bReadSource)
		{
			fdp = new CFileReadStream(szFile, off, length);
		}
		else
		{
			fdp = new CFileWriteStream(szFile, off, bAppend);
		}
	}
	catch(std::runtime_error &e)
	{
		err() << e.what() << endl;
		return NULL;
	}

	if (!fdp)
	{
		err() << "system is out of memeory!" << endl;
	}

	return fdp;
}

CStream *CTransfer::getBufStream(char *buffer, off64_t length, bool bRelease)
{
	if (!buffer || length <= 0)
	{
		err() << "Invalid args: " << " buffer=" << buffer << " length=" << length << endl;
		return NULL;
	}

	CBufReadStream *stream = new (std::nothrow) CBufReadStream(buffer, length, bRelease);
	if (!stream)
	{
		err() << "system is out of memeory!" << endl;
	}

	return stream;
}

CStream *CTransfer::getConsoleStream(off64_t off, off64_t length, bool bOutput)
{
	if (off < 0)
	{
		err() << "Invalid args: stream('console'," << off << "," << length << ")" << endl;
		return NULL;
	}

	CConsoleStream *stream = new (std::nothrow) CConsoleStream(off, length, bOutput);
	if (!stream)
	{
		err() << "system is out of memeory!" << endl;
		return NULL;
	}

	return stream;
}

void CTransfer::release(CStream *fdp)
{
	string s;
	
	trace() << "release stream:" << fdp->name() << "/" << fdp->detail(s) << " ..." << endl;
	delete fdp;
}

bool CTransfer::transfer(char *buffer, off64_t length)
{
	off64_t res;

	for (off64_t cur = 0; cur < length; cur += res)
	{
		res = m_dfdp->write(buffer + cur, length - cur);
		if (res <= 0)
		{
			string s;
			err() << "transfer buffer(" << std::hex << (void *)buffer << "," << std::dec << length << ") to stream(" 
					 << m_dfdp->name() << "/" << m_dfdp->detail(s) << ") err:" << m_dfdp->errmsg() << endl;
			return false;
		}
	}

	return true;
}

bool CTransfer::transfer(CStream *sfdp, off64_t &transfered)
{	
	string s, ds;
	
	debug() << "transfering stream(" << sfdp->name() << "/" << sfdp->detail(s) << ") to stream(" 
		   << m_dfdp->name() << "/" << m_dfdp->detail(ds) << ") ..." << endl;
	if (!m_buf)
	{
		if (!setTransferBuffer(1024 * 1024 * 4))
		{
			err() << "system is out of memory !" << endl;
			return false;
		}
	}
	transfered = 0;
	
	off64_t lastPrompt = 0;
	for (bool succ = true; succ;)
	{
		off64_t len = m_buflen;
		char *datas = sfdp->read(m_buf, len);
			
		if (!datas)
		{
			if (sfdp->eof())
			{
				info() << "stream(" << sfdp->name() << "/" << sfdp->detail(s) << ") is EOF. transfered " << transfered << " bytes" << endl;
				break;
			}
			else
			{
				err() << "read stream(" << sfdp->name() << "/" << sfdp->detail(s) << ") at:" << sfdp->tellg() << " err:" << sfdp->errmsg() << endl;
				succ = false;
			}
		}
		else
		{	
			succ = transfer(datas, len);
		}

		if (!succ)
		{
			err() << "Failed to transfer datas from stream(" << sfdp->name() << "/" << sfdp->detail(s) << ") to stream("
					 << m_dfdp->name() << "/" << m_dfdp->detail(ds) << ") transfered " << transfered << " bytes. sfdp.tellg()=" 
					 << sfdp->tellg() << " dfdp.tellg()" << m_dfdp->tellp() << endl; 
			return false;
		}
		else
		{
			transfered += len;
			
			if (sfdp->capacity() > 0)
			{
				double percent =  (double)(sfdp->tellg() - lastPrompt)/ sfdp->capacity();
				if (percent >= 0.1);
				{
					trace() << "stream(" << sfdp->name() << "/" << sfdp->detail(s) << ") => stream("
							 << m_dfdp->name() << "/" << m_dfdp->detail(ds) << ") " << sfdp->tellg() * (double)100 / sfdp->capacity() 
							 << "%" << endl;
					lastPrompt = sfdp->tellg();
				}
			}
		}
	}

	return true;
}

bool CTransfer::setOutputStream(CStream *dfdp)
{
	if (m_dfdp)
	{
		release(m_dfdp);
	}
	
	string s;

	info() << "setOutputStream:" << dfdp->name() << "/" << dfdp->detail(s) << endl;
	m_dfdp = dfdp;
	
	return true;
}

bool CTransfer::setOutputFile(const char *szOutputFile, off64_t off, bool bAppend)
{
	CStream *fdp = getWFStream(szOutputFile, off, bAppend);

	if (!fdp)
	{
		err() << "setOutputFile('" << szOutputFile << "') = false" << endl;
		return false;
	}
	
	return setOutputStream(fdp);
}

bool CTransfer::setTransferBuffer(size_t length)
{
	if (m_buf)
	{
		delete [] m_buf;
	}
	m_buflen = length;
	m_buf = new (std::nothrow) char[length];
	
	info() << "setTransferBuffer:" << (double)length/1024 << " k" << endl;
	return m_buf != NULL;
}

bool CTransfer::addSource(CStream *sfdp)
{
	string s;
	
	info() << "addSource(" << sfdp->name() << "/" << sfdp->detail(s) << ")" << endl;
	m_sfdpQ.push_back(sfdp);
	
	return true;
}

bool CTransfer::addFileSource(const char *szFile, off64_t off, off64_t length)
{
	CStream *fdp = getRFStream(szFile, off, length);

	if (!fdp)
	{
		err() << "addSource('" << szFile << "', " << off << ", " << length << ") = false" << endl;
		return false;
	}

	return addSource(fdp);
}

bool CTransfer::addBufferSource(char *buffer, off64_t length, bool bRelease)
{
	CStream *fdp = getBufStream(buffer, length, bRelease);

	if (!fdp)
	{
		err() << "addSource('" << buffer << "'," << length << ") = false" << endl;
		return false;
	}

	return addSource(fdp);
}

bool CTransfer::addConsoleSource(off64_t off, off64_t length)
{
	CStream *fdp = getConsoleStream(off, length);
	
	if (!fdp)
	{
		err() << "getConsole(" << off << "," << length << ") = false" << endl;
		return false;
	}
	
	return addSource(fdp);
}

bool CTransfer::transfer(const char *szFile, off64_t off, off64_t length)
{
	CStream *fdp = getFStream(szFile, off, length, true);
	
	if (!fdp)
	{
		err() << "getSfdp('" << szFile<< "'," << off << "," << length << ") = false" << endl;
		return false;
	}
	
	off64_t bytes = 0;
	bool succ = transfer(fdp, bytes);
	release(fdp);

	return succ;
}

bool CTransfer::transfer()
{
	string ds;
	CStream *fdp;
	off64_t total = 0, bytes = 0;
	bool succ = true;
	
	info() << "start transfering ..." << endl;
	while (succ && (fdp = get()))
	{
		succ = transfer(fdp, bytes);
		if (!succ)
		{
			string s;
			err() << "Failed to transfer stream from " << fdp->name() << "/" << fdp->detail(s) << "  to "
				  << m_dfdp->name() << "/" << m_dfdp->detail(ds) << endl;
		}
		release(fdp);
		total += bytes;
	}
	info() << "Done. " << total << " bytes has been transfered to stream(" << m_dfdp->name() << "/" << m_dfdp->detail(ds) << ")" << endl;

	return succ;
}

CStream *CTransfer::get()
{
	CStream *fdp = NULL;

	if (!m_sfdpQ.empty())
	{
		fdp = *m_sfdpQ.begin();
		m_sfdpQ.erase(m_sfdpQ.begin());
	}

	return fdp;
}

void usage(const char *exe)
{
	cerr << "============================================" << endl
		 << "COPYRIGHT (C) 2014 - 2020, piggy_xrh        " << endl
	     << "A tool to merge the big file (version-1.0.0)" << endl
  	  	 << "(Email: piggy_xrh@163.com  QQ: 1169732280)  " << endl
		 << "============================================" << endl
		 << endl
		 << exe << " [-o 输出文件[:偏移]] [-a] [-f 输入文件[:偏移[:长度]],输入文件[:偏移[:长度]],...] " 
		 << "[-c偏移[:长度]] [-l 日志级别] [-b 缓冲大小]" << endl;
}


void split(const string& s, const string& delim, vector<string> &vec)  
{  
	size_t last  = 0;  
	size_t index = s.find_first_of(delim, last);  
	
	while (index != std::string::npos)  
	{  
		vec.push_back(s.substr(last, index-last));  
		last  = index + 1;  
		index = s.find_first_of(delim, last);  
	}  

	if (index - last>0)  
	{  
		vec.push_back(s.substr(last, index - last));  
	}  
}  

int main(int argc, char *argv[])
{
	int opt;
	struct option lopt[] = 
	{
		{"help",    0, NULL, 'h'},
		{"to",      1, NULL, 'o'},
		{"append",  0, NULL, 'a'},
		{"fs",      1, NULL, 'f'},
		{"console", 2, NULL, 'c'},
		{"logl",    1, NULL, 'l'},
		{"buffer",  1, NULL, 'b'},
		{NULL,      0, NULL,  0}
	};
	string files, to;
	string console;
	bool bAppend = false;
	bool bAddConsole = false;
	size_t bufsize = 0;

	// Parse the options
	
	while ((opt = getopt_long(argc, argv, "hl:o:f:b:ac::", lopt, NULL)) != -1)
	{
		switch (opt)
		{
			case 'h':
				usage(argv[0]);
				exit(0);
				break;
			case 'o':
				to = optarg;
				break;
			case 'a':
				bAppend = true;
				break;
			case 'f':
				files = optarg;
				break;
			case 'c':
				bAddConsole = true;
				console = (optarg != NULL) ? optarg : "";
				break;
			case 'b':
				bufsize = atoi(optarg);
				break;
			case 'l':
				g_logLevel = max((int)L_TRACE, min((int)L_ERROR, atoi(optarg)));
				break;
		}
	}

	CTransfer *transfer;
	
	// If there is none any output stream, we redirect the datas to the console

	if (to.empty())
	{
		transfer = new CTransfer(CTransfer::getConsoleStream(0, -1, true));
	}
	else
	{
		vector<string> vec;
		
		split(to, ":", vec);
		if (vec.size() == 1)
		{
			vec.push_back("0");
		}
		transfer = new CTransfer(vec[0].c_str(), atoll(vec[1].c_str()), bAppend);
	}
	
	// Add the file streams

	if (!files.empty())
	{
		vector<string> vec;
		
		split(files, ",", vec);
		for (vector<string>::iterator it = vec.begin(); it != vec.end(); it ++)
		{
			vector<string> vecFs;

			split(*it, ":", vecFs);
			if (vecFs.size() == 1)
			{
				vecFs.push_back("0");
			}
			vecFs.push_back("-1");
			assert (vecFs.size() >= 3);
			transfer->addFileSource(vecFs[0].c_str(), atoll(vecFs[1].c_str()), atoll(vecFs[2].c_str()));
		}
	}
	
	// Add the console stream
	
	if (!console.empty())
	{
		vector<string> vec;
		
		split(console, ":", vec);
		if (vec.size() < 2)
		{
			vec.push_back("-1");
		}
		transfer->addConsoleSource(atoll(vec[0].c_str()), atoll(vec[1].c_str()));
	}
	
	// We add the console stream into transfer by default
	
	else if (bAddConsole || files.empty())
	{
		transfer->addConsoleSource();
	}

	if (bufsize > 0)
	{
		transfer->setTransferBuffer(bufsize);
	}
	
	// We start to transfer the datas if we are ready now

	if (*transfer)
	{
		transfer->transfer();
	}

	return 0;
}

